package de.umr.fb12.vs.os;

import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Core Scheduler is the main part of this Simulation Framework. All components
 * are brought together in this class. It forwards system calls and IRQs to the
 * scheduling classes and is responsible for querying the scheduler classes for
 * new tasks.
 * 
 * 
 */
public final class CoreScheduler {
	private static List<int[]> programs = new ArrayList<int[]>(10);
	private static List<String> program_names = new ArrayList<String>(10);
	private static List<SchedClass> sched_classes = new LinkedList<SchedClass>();
	private static CPU cpu;
	private static boolean verbose = true;

	// tick irq hanlder
	static IRQHandler tick = new IRQHandler() {
		public void handle_IRQ(int irq, Object o) {
			// trigger tick for every registered scheduler
			// tick occurs every 150 ms see IRQController.java for details.
			for (SchedClass sched : sched_classes)
				sched.task_tick(cpu.current_task);
			schedule();
		}
	};

	// handler for IO return IRQ
	static IRQHandler io_ret = new IRQHandler() {
		@Override
		public void handle_IRQ(int irq, Object o) {
			Task t = (Task) o;
			t.state = Task.STATES.RUNNABLE;
			// (re)-enqueue the task to the scheduler which is responsible for
			// the task
			t.sched_class.enqueue_task(cpu.current_task, t);
			schedule();
		}
	};

	// IRQ handler for syscalls. NOTE: here are IRQ only used for entry this is
	// not a handling of an asynchronous IRQ.
	static IRQHandler sys_call = new IRQHandler() {
		@Override
		public void handle_IRQ(int irq, Object o) {
			handle_syscall((Task) o);
			schedule();
		}
	};

	/**
	 * Initialization of the Core Scheulder.
	 * 
	 * @param scheduler
	 *            the scheduler class which shell be used for the the init task
	 *            and all spawned tasks
	 * @param initTask
	 *            the path to the asm source file for the init task
	 * @param dummyTask
	 *            the path to the asm source file for the dummy task
	 */
	public static void init(SchedClass scheduler, String initTask,
			String dummyTask) {
		// compile the programs
		loadProgram("asm/idle.asm");
		loadProgram(initTask);
		loadProgram(dummyTask);

		// create a new CPU
		cpu = new CPU();

		// create a new idle task
		Task idle_task = new Task(cpu);
		idle_task.loadProgram(programs.get(0));
		idle_task.name = program_names.get(0);
		idle_task.idle_task = true;

		// create an IdleScheduler
		SchedClass idleSched = new IdleSched(idle_task);

		// set the idle_tasks sched_class to the idleScheduler
		idle_task.sched_class = idleSched;
		verbose(String.format("task created pid: %d (%s)", idle_task.pid,
				idle_task.name));

		// add passed and idle sched to the sched_classes list
		sched_classes.add(scheduler);
		sched_classes.add(idleSched);
		// create the init_task
		Task init_task = new Task(cpu);
		init_task.loadProgram(programs.get(1));
		init_task.name = program_names.get(1);

		// set the init_tasks scheduler to the passed one
		init_task.sched_class = scheduler;
		verbose(String.format("task created pid: %d (%s)", init_task.pid,
				init_task.name));

		// register the handler at our cpu
		cpu.register_irq_handler(0x00, tick);
		cpu.register_irq_handler(0x01, io_ret);
		cpu.register_irq_handler(0x80, sys_call);

		// assign the init_task to its CPU.
		init_task.switch_to();

		// enable IRQs
		cpu.enable_local_IRQs();

	}

	/**
	 * Auxiliary method for handling syscalls
	 * 
	 * @param t
	 *            the task which issued the syscall
	 */
	static void handle_syscall(Task t) {
		switch (t.syscall) {
		// if a task does a IO request set its state to block and dequeue it
		// from its scheduler
		case IOREQ:
			t.state = Task.STATES.BLOCKED;
			t.sched_class.dequeue_task(cpu.current_task, t);
			break;
		case DUMMY:
			// as it means do nothing
			break;
		case EXIT:
			// if a task exits set its state to dead and dequeue it from its
			// scheduler
			verbose(String.format("task exited pid: %d (%s)", t.pid, t.name));
			t.state = Task.STATES.DEAD;
			t.sched_class.dequeue_task(cpu.current_task, t);
			break;
		case EXECVE:
			// a bit inconvenient way of task creation: create a new task
			// structure for the CPU and the scheduler the father belongs to;
			// select the program source and call add on its scheduler class
			Task newtsk = new Task(t.cpu);
			newtsk.loadProgram(programs.get((Integer) t.syscall_data));
			newtsk.name = program_names.get((Integer) t.syscall_data);
			newtsk.sched_class = t.sched_class;
			verbose(String.format("task created pid: %d (%s)", newtsk.pid,
					newtsk.name));
			t.sched_class.task_new(t, newtsk);
			break;
		case YIELD:
			// just call yield on the tasks schedclass
			t.sched_class.yield_task(cpu.current_task, t);
		default:
			throw new RuntimeException(
					"Something weird happend to the syscalls");
		}
	}

	/**
	 * Sets the flag if the CoreScheduler shell print the process switches
	 * 
	 * @param verbose
	 *            true for verbose, false otherwise
	 */
	public static void setVerbose(boolean verbose) {
		CoreScheduler.verbose = verbose;
	}

	/**
	 * Called every time if the CPU returns to execute a task (syscalls,
	 * handling IRQs,...)
	 */
	static private void schedule() {
		// if a scheduler class set the resched flag for the current task a task
		// switch is issued
		if (cpu.current_task.need_resched()) {
			// give the current task back to its scheduler
			cpu.current_task.sched_class.put_prev_task(cpu.current_task);
			cpu.current_task.clear_tsk_need_resched();
			// ask all scheduler classes hierarchically for the next task
			// (IdleSched always has a task)
			Task next = null;
			for (SchedClass sched : sched_classes) {
				if ((next = sched.pick_next_task()) != null)
					break;
			}
			verbose(cpu.current_task.pid + " -> " + next.pid);
			//
			next.switch_to();
		}
	}

	/**
	 * Auxiliary method for verbose-"sysouts"
	 * 
	 * @param msg
	 *            the message
	 */
	final static void verbose(String msg) {
		if (verbose)
			System.out.println(msg);
	}

	/**
	 * Auxiliary method for program loading/source assembling
	 * 
	 * @param file
	 */
	private static void loadProgram(String file) {
		// auxilliary method for source assembling
		programs.add(new Assembler().assemble(new File(file)));
		program_names.add(file.split("/")[file.split("/").length - 1]);
	}

	/**
	 * Can be called to list all processes.
	 * 
	 * @return the process table as formatted String
	 */
	public static String proc_table() {
		StringBuilder sb = new StringBuilder();
		for (Task t : Task.task_list) {
			sb.append(String.format("%3d\t%15d\t%15d\t%15d%n", t.pid, t.usr,
					t.sys, t.iow));
		}
		return sb.toString();
	}
}